/* 泛型函数 */

// 函数的输出类型依赖函数的输入类型，
// 或者两个输入的类型以某种形式相互关联。

// 实现一个函数：返回数组的第一个元素
function firstElement(arr: any[]) {
  return arr[0];
}

// TypeScript中，泛型就是被用来描述两个值之间的对应关系
function firstElement1<Type>(arr: Type[]): Type | undefined {
  return arr[0];
}

// 通过给函数添加一个类型参数 Type，并且在两个地方使用它，
// 我们就在函数的输入(即数组)和函数的输出(即返回值)之间创建了一个关联
// s is of type 'string'
const s5 = firstElement1(["a", "b", "c"]);
// n is of type 'number'
const n5 = firstElement1([1, 2, 3]);
// u is of type undefined
const u5 = firstElement1([]);

// 推断==============================================================================================================
// TypeScript 既可以推断出 Input 的类型 （从传入的 string 数组），
// 又可以根据函数表达式的返回值推断出 Output 的类型。
function map<Input, OutPut>(arr: Input[], func: (arg: Input) => OutPut): OutPut[] {
  return arr.map(func);
}
const parsed = map(["1", "2", "3"], (n) => parseInt(n));

// 约束==============================================================================================================
// 对 Type 做了 { length: number } 限制，我们才可以被允许获取 a b参数的 .length 属性。没有这个类型约束，我们甚至不能获取这些属性，
// 因为这些值也许是其他类型，并没有 length 属性
function longest<Type extends { length: number }>(a: Type, b: Type) {
  if (a.length >= b.length) {
    return a;
  } else {
    return b;
  }
}

// longerArray is of type 'number[]'
const longerArray = longest([1, 2], [1, 2, 3]);
// longerString is of type 'alice' | 'bob'
const longerString = longest("alice", "bob");
// Error! Numbers don't have a 'length' property
const notOK = longest(10, 100);
// Argument of type 'number' is not assignable to parameter of type '{ length: number; }'.


// 泛型约束实战=====================================================================================
// 其中的问题就在于函数理应返回与传入参数相同类型的对象，而不仅仅是符合约束的对象
function minimumLength<Type extends { length: number }>(
  obj: Type,
  minimum: number
): Type {
  if (obj.length >= minimum) {
    return obj;
  } else {
    return { length: minimum };
    // Type '{ length: number; }' is not assignable to type 'Type'.
    // '{ length: number; }' is assignable to the constraint of type 'Type', but 'Type' could be instantiated with a different subtype of constraint '{ length: number; }'.
  }
}
// 'arr' gets value { length: 6 }
const arr = minimumLength([1, 2, 3], 6);
// and crashes here because arrays have
// a 'slice' method, but not the returned object!
console.log(arr.slice(0));


// 声明类型参数
function combine2<Type>(arr1: Type[], arr2: Type[]): Type[] {
  return arr1.concat(arr2);
}

const arr12 = combine2([1, 2, 3], ["hello"]);
// Type 'string' is not assignable to type 'number'.



function combine12<Type1>(arr1: Type1[], arr2: Type1[]): Type1[] {
  return arr1.concat(arr2);
}

const arr22 = combine12<number | string>([1, 2, 3], ["hello"]);


// 写一个好的泛型函数的一些建议===========================================================================
// 1、类型参数下移（Push Type Parameters Down）
function firstElement12<Type>(arr: Type[]) { // 泛型约束了数组中元素的类型
  return arr[0];
}

function firstElement22<Type extends any[]>(arr: Type) { // 泛型约束了一个数组，但是元素没有被约束
  return arr[0];
}
// TypeScript 不得不用约束的类型来推断 arr[0] 表达式，
// 而不是等到函数调用的时候再去推断这个元素。
// a: number (good)
const a12 = firstElement12([1, 2, 3]);
// b: any (bad)
const b12 = firstElement22([1, 2, 3]);


// 2、使用更少的类型参数 (Use Fewer Type Parameters)
function filter12<Type>(arr: Type[], func: (arg: Type) => boolean): Type[] {
  return arr.filter(func);
}
filter12([1,2,3], (item) => item > 2)

// Func 什么也没做，却导致函数更难阅读和推断。
function filter22<Type, Func extends (arg: Type) => boolean>(
  arr: Type[],
  func: Func
): Type[] {
  return arr.filter(func);
}
filter22([1,2,3], (item) => item > 2)

// 3、类型参数应该出现两次
// 有时候我们会忘记一个函数其实并不需要泛型
function greet<Str extends string>(s: Str) {
  console.log("Hello, " + s);
}

greet("world");
// 另一种写法
// ！！类型参数是用来关联多个值之间的类型。
// 如果一个类型参数只在函数签名里出现了一次，那它就没有跟任何东西产生关联。
function greet(s: string) {
  console.log("Hello, " + s);
}
